﻿using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using PMS.Core.Utils.Authentication;
using PMS.Data.Entities.Customers;
using PMS.Services.EntityServices.Customers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Claims;

namespace PMS.Services.Authentication
{
    /// <summary>
    /// Represents service using cookie middleware for the authentication
    /// </summary>
    public partial class CookieAuthenticationService : IAuthenticationService
    {
        #region Fields

        private readonly ICustomerService _customerService;
        private readonly IHttpContextAccessor _httpContextAccessor;

        private Customer _cachedCustomer;

        #endregion

        #region Ctor

        public CookieAuthenticationService(ICustomerService customerService,
            IHttpContextAccessor httpContextAccessor)
        {
            _customerService = customerService;
            _httpContextAccessor = httpContextAccessor;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Sign in
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="isPersistent">Whether the authentication session is persisted across multiple requests</param>
        public virtual async void SignIn(Customer customer, bool isPersistent)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            var claims = new List<Claim>();

            if (customer.CustomerGuid != Guid.Empty)
                claims.Add(new Claim(ClaimTypes.NameIdentifier, customer.CustomerGuid.ToString(), ClaimValueTypes.String, AuthenticationDefaultConfigs.ClaimsIssuer));

            if (!string.IsNullOrEmpty(customer.Mobile))
                claims.Add(new Claim(ClaimTypes.MobilePhone, customer.Mobile, ClaimValueTypes.String, AuthenticationDefaultConfigs.ClaimsIssuer));

            DateTime authTime = DateTime.UtcNow;
            DateTime expiresAt = authTime.AddMinutes(Convert.ToDouble(AuthenticationDefaultConfigs.CookieExpirationMinutes));
            claims.Add(new Claim(ClaimTypes.Expiration, expiresAt.ToString(), ClaimValueTypes.DateTime, AuthenticationDefaultConfigs.ClaimsIssuer));
            //create principal for the current authentication scheme
            var userIdentity = new ClaimsIdentity(claims, AuthenticationDefaultConfigs.DefaultAuthenticationScheme);
            var userPrincipal = new ClaimsPrincipal(userIdentity);

            //set value indicating whether session is persisted and the time at which the authentication was issued
            var authenticationProperties = new AuthenticationProperties
            {
                IsPersistent = isPersistent,
                IssuedUtc = authTime,
                ExpiresUtc = expiresAt
            };

            //sign in
            await _httpContextAccessor.HttpContext.SignInAsync(AuthenticationDefaultConfigs.DefaultAuthenticationScheme, userPrincipal, authenticationProperties);

            //cache authenticated customer
            _cachedCustomer = customer;
        }

        /// <summary>
        /// Sign out
        /// </summary>
        public virtual async void SignOut()
        {
            //reset cached customer
            _cachedCustomer = null;
            //and sign out from the current authentication scheme
            await _httpContextAccessor.HttpContext.SignOutAsync(AuthenticationDefaultConfigs.DefaultAuthenticationScheme);
        }

        /// <summary>
        /// Get authenticated customer
        /// </summary>
        /// <returns>Customer</returns>
        public virtual Customer GetAuthenticatedCustomer()
        {
            //whether there is a cached customer
            if (_cachedCustomer != null)
                return _cachedCustomer;

            string schema = AuthenticationDefaultConfigs.DefaultAuthenticationScheme;
            string issuer = AuthenticationDefaultConfigs.ClaimsIssuer;
            Customer customer = null;
           
            var authenticateResult = _httpContextAccessor.HttpContext.AuthenticateAsync(schema).Result;
            #region Obsoleted_GetExpirationTime
            //https://stackoverflow.com/questions/57098973/how-to-get-asp-net-identity-authentication-ticket-expiry-in-razor-page
            //if (authenticateResult.Properties.ExpiresUtc.HasValue)
            //{
            //    var secondsRemaining = authenticateResult.Properties.ExpiresUtc.Value
            //        .Subtract(DateTime.UtcNow)
            //        .TotalSeconds;
            //    if (secondsRemaining < 0 && !authenticateResult.Succeeded)
            //        throw new SecurityTokenExpiredException($"{schema} Authentication Expired");
            //}
            #endregion

            if (!authenticateResult.Succeeded)
                return null;

            //try to get customer by username
            var usermobileClaim = authenticateResult.Principal.FindFirst(claim => claim.Type == ClaimTypes.MobilePhone
                && claim.Issuer.Equals(issuer, StringComparison.InvariantCultureIgnoreCase));
            if (usermobileClaim != null)
                customer = _customerService.GetCustomerByMobile(usermobileClaim.Value);
            if (customer == null)
            {
                //try to get customer by Guid
                var guidClaim = authenticateResult.Principal.FindFirst(claim => claim.Type == ClaimTypes.NameIdentifier
                    && claim.Issuer.Equals(issuer, StringComparison.InvariantCultureIgnoreCase));
                if (guidClaim != null)
                    customer = _customerService.GetCustomerByGuid((Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFrom(guidClaim.Value));
            }

            //whether the found customer is available
            if (customer == null || !customer.IsActive || customer.IsDeleted)
                return null;

            //cache authenticated customer
            _cachedCustomer = customer;

            return _cachedCustomer;
        }

        #endregion
    }
}
